// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <unordered_map>

#include "src/assembler-inl.h"
#include "src/assert-scope.h"
#include "src/base/optional.h"
#include "src/compiler/wasm-compiler.h"
#include "src/debug/debug-scopes.h"
#include "src/debug/debug.h"
#include "src/frames-inl.h"
#include "src/heap/factory.h"
#include "src/identity-map.h"
#include "src/isolate.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-interpreter.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/zone/accounting-allocator.h"

namespace v8 {
namespace internal {
    namespace wasm {

        namespace {

            template <bool internal, typename... Args>
            Handle<String> PrintFToOneByteString(Isolate* isolate, const char* format,
                Args... args)
            {
                // Maximum length of a formatted value name ("arg#%d", "local#%d",
                // "global#%d", i32 constants, i64 constants), including null character.
                static constexpr int kMaxStrLen = 21;
                EmbeddedVector<char, kMaxStrLen> value;
                int len = SNPrintF(value, format, args...);
                CHECK(len > 0 && len < value.length());
                Vector<uint8_t> name = Vector<uint8_t>::cast(value.SubVector(0, len));
                return internal
                    ? isolate->factory()->InternalizeOneByteString(name)
                    : isolate->factory()->NewStringFromOneByte(name).ToHandleChecked();
            }

            Handle<Object> WasmValueToValueObject(Isolate* isolate, WasmValue value)
            {
                switch (value.type()) {
                case kWasmI32:
                    if (Smi::IsValid(value.to<int32_t>()))
                        return handle(Smi::FromInt(value.to<int32_t>()), isolate);
                    return PrintFToOneByteString<false>(isolate, "%d", value.to<int32_t>());
                case kWasmI64: {
                    int64_t i64 = value.to<int64_t>();
                    int32_t i32 = static_cast<int32_t>(i64);
                    if (i32 == i64 && Smi::IsValid(i32))
                        return handle(Smi::FromIntptr(i32), isolate);
                    return PrintFToOneByteString<false>(isolate, "%" PRId64, i64);
                }
                case kWasmF32:
                    return isolate->factory()->NewNumber(value.to<float>());
                case kWasmF64:
                    return isolate->factory()->NewNumber(value.to<double>());
                default:
                    UNIMPLEMENTED();
                    return isolate->factory()->undefined_value();
                }
            }

            MaybeHandle<String> GetLocalName(Isolate* isolate,
                Handle<WasmDebugInfo> debug_info,
                int func_index, int local_index)
            {
                DCHECK_LE(0, func_index);
                DCHECK_LE(0, local_index);
                if (!debug_info->has_locals_names()) {
                    Handle<WasmModuleObject> module_object(
                        debug_info->wasm_instance()->module_object(), isolate);
                    Handle<FixedArray> locals_names = DecodeLocalNames(isolate, module_object);
                    debug_info->set_locals_names(*locals_names);
                }

                Handle<FixedArray> locals_names(debug_info->locals_names(), isolate);
                if (func_index >= locals_names->length() || locals_names->get(func_index)->IsUndefined(isolate)) {
                    return {};
                }

                Handle<FixedArray> func_locals_names(
                    FixedArray::cast(locals_names->get(func_index)), isolate);
                if (local_index >= func_locals_names->length() || func_locals_names->get(local_index)->IsUndefined(isolate)) {
                    return {};
                }
                return handle(String::cast(func_locals_names->get(local_index)), isolate);
            }

            class InterpreterHandle {
                MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(InterpreterHandle);
                Isolate* isolate_;
                const WasmModule* module_;
                WasmInterpreter interpreter_;
                StepAction next_step_action_ = StepNone;
                int last_step_stack_depth_ = 0;
                std::unordered_map<Address, uint32_t> activations_;

                uint32_t StartActivation(Address frame_pointer)
                {
                    WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
                    uint32_t activation_id = thread->StartActivation();
                    DCHECK_EQ(0, activations_.count(frame_pointer));
                    activations_.insert(std::make_pair(frame_pointer, activation_id));
                    return activation_id;
                }

                void FinishActivation(Address frame_pointer, uint32_t activation_id)
                {
                    WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
                    thread->FinishActivation(activation_id);
                    DCHECK_EQ(1, activations_.count(frame_pointer));
                    activations_.erase(frame_pointer);
                }

                std::pair<uint32_t, uint32_t> GetActivationFrameRange(
                    WasmInterpreter::Thread* thread, Address frame_pointer)
                {
                    DCHECK_EQ(1, activations_.count(frame_pointer));
                    uint32_t activation_id = activations_.find(frame_pointer)->second;
                    uint32_t num_activations = static_cast<uint32_t>(activations_.size() - 1);
                    uint32_t frame_base = thread->ActivationFrameBase(activation_id);
                    uint32_t frame_limit = activation_id == num_activations
                        ? thread->GetFrameCount()
                        : thread->ActivationFrameBase(activation_id + 1);
                    DCHECK_LE(frame_base, frame_limit);
                    DCHECK_LE(frame_limit, thread->GetFrameCount());
                    return { frame_base, frame_limit };
                }

                static ModuleWireBytes GetBytes(WasmDebugInfo debug_info)
                {
                    // Return raw pointer into heap. The WasmInterpreter will make its own copy
                    // of this data anyway, and there is no heap allocation in-between.
                    NativeModule* native_module = debug_info->wasm_instance()->module_object()->native_module();
                    return ModuleWireBytes { native_module->wire_bytes() };
                }

            public:
                InterpreterHandle(Isolate* isolate, Handle<WasmDebugInfo> debug_info)
                    : isolate_(isolate)
                    , module_(debug_info->wasm_instance()->module_object()->module())
                    , interpreter_(isolate, module_, GetBytes(*debug_info),
                          handle(debug_info->wasm_instance(), isolate))
                {
                }

                ~InterpreterHandle() { DCHECK_EQ(0, activations_.size()); }

                WasmInterpreter* interpreter() { return &interpreter_; }
                const WasmModule* module() const { return module_; }

                void PrepareStep(StepAction step_action)
                {
                    next_step_action_ = step_action;
                    last_step_stack_depth_ = CurrentStackDepth();
                }

                void ClearStepping() { next_step_action_ = StepNone; }

                int CurrentStackDepth()
                {
                    DCHECK_EQ(1, interpreter()->GetThreadCount());
                    return interpreter()->GetThread(0)->GetFrameCount();
                }

                // Returns true if exited regularly, false if a trap/exception occurred and
                // was not handled inside this activation. In the latter case, a pending
                // exception will have been set on the isolate.
                bool Execute(Handle<WasmInstanceObject> instance_object,
                    Address frame_pointer, uint32_t func_index,
                    Vector<WasmValue> argument_values,
                    Vector<WasmValue> return_values)
                {
                    DCHECK_GE(module()->functions.size(), func_index);
                    FunctionSig* sig = module()->functions[func_index].sig;
                    DCHECK_EQ(sig->parameter_count(), argument_values.size());
                    DCHECK_EQ(sig->return_count(), return_values.size());

                    uint32_t activation_id = StartActivation(frame_pointer);

                    WasmCodeRefScope code_ref_scope;
                    WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
                    thread->InitFrame(&module()->functions[func_index],
                        argument_values.start());
                    bool finished = false;
                    while (!finished) {
                        // TODO(clemensh): Add occasional StackChecks.
                        WasmInterpreter::State state = ContinueExecution(thread);
                        switch (state) {
                        case WasmInterpreter::State::PAUSED:
                            NotifyDebugEventListeners(thread);
                            break;
                        case WasmInterpreter::State::FINISHED:
                            // Perfect, just break the switch and exit the loop.
                            finished = true;
                            break;
                        case WasmInterpreter::State::TRAPPED: {
                            MessageTemplate message_id = WasmOpcodes::TrapReasonToMessageId(thread->GetTrapReason());
                            Handle<Object> exception = isolate_->factory()->NewWasmRuntimeError(message_id);
                            auto result = thread->RaiseException(isolate_, exception);
                            if (result == WasmInterpreter::Thread::HANDLED)
                                break;
                            // If no local handler was found, we fall-thru to {STOPPED}.
                            DCHECK_EQ(WasmInterpreter::State::STOPPED, thread->state());
                            V8_FALLTHROUGH;
                        }
                        case WasmInterpreter::State::STOPPED:
                            // An exception happened, and the current activation was unwound
                            // without hitting a local exception handler. All that remains to be
                            // done is finish the activation and let the exception propagate.
                            DCHECK_EQ(thread->ActivationFrameBase(activation_id),
                                thread->GetFrameCount());
                            DCHECK(isolate_->has_pending_exception());
                            FinishActivation(frame_pointer, activation_id);
                            return false;
                        // RUNNING should never occur here.
                        case WasmInterpreter::State::RUNNING:
                        default:
                            UNREACHABLE();
                        }
                    }

                    // Copy back the return value.
                    DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count());
                    // TODO(wasm): Handle multi-value returns.
                    DCHECK_EQ(1, kV8MaxWasmFunctionReturns);
                    if (sig->return_count()) {
                        return_values[0] = thread->GetReturnValue(0);
                    }

                    FinishActivation(frame_pointer, activation_id);

                    return true;
                }

                WasmInterpreter::State ContinueExecution(WasmInterpreter::Thread* thread)
                {
                    switch (next_step_action_) {
                    case StepNone:
                        return thread->Run();
                    case StepIn:
                        return thread->Step();
                    case StepOut:
                        thread->AddBreakFlags(WasmInterpreter::BreakFlag::AfterReturn);
                        return thread->Run();
                    case StepNext: {
                        int stack_depth = thread->GetFrameCount();
                        if (stack_depth == last_step_stack_depth_)
                            return thread->Step();
                        thread->AddBreakFlags(stack_depth > last_step_stack_depth_
                                ? WasmInterpreter::BreakFlag::AfterReturn
                                : WasmInterpreter::BreakFlag::AfterCall);
                        return thread->Run();
                    }
                    default:
                        UNREACHABLE();
                    }
                }

                Handle<WasmInstanceObject> GetInstanceObject()
                {
                    StackTraceFrameIterator it(isolate_);
                    WasmInterpreterEntryFrame* frame = WasmInterpreterEntryFrame::cast(it.frame());
                    Handle<WasmInstanceObject> instance_obj(frame->wasm_instance(), isolate_);
                    // Check that this is indeed the instance which is connected to this
                    // interpreter.
                    DCHECK_EQ(this, Managed<InterpreterHandle>::cast(instance_obj->debug_info()->interpreter_handle())->raw());
                    return instance_obj;
                }

                void NotifyDebugEventListeners(WasmInterpreter::Thread* thread)
                {
                    // Enter the debugger.
                    DebugScope debug_scope(isolate_->debug());

                    // Check whether we hit a breakpoint.
                    if (isolate_->debug()->break_points_active()) {
                        Handle<WasmModuleObject> module_object(
                            GetInstanceObject()->module_object(), isolate_);
                        int position = GetTopPosition(module_object);
                        Handle<FixedArray> breakpoints;
                        if (WasmModuleObject::CheckBreakPoints(isolate_, module_object, position)
                                .ToHandle(&breakpoints)) {
                            // We hit one or several breakpoints. Clear stepping, notify the
                            // listeners and return.
                            ClearStepping();
                            isolate_->debug()->OnDebugBreak(breakpoints);
                            return;
                        }
                    }

                    // We did not hit a breakpoint, so maybe this pause is related to stepping.
                    bool hit_step = false;
                    switch (next_step_action_) {
                    case StepNone:
                        break;
                    case StepIn:
                        hit_step = true;
                        break;
                    case StepOut:
                        hit_step = thread->GetFrameCount() < last_step_stack_depth_;
                        break;
                    case StepNext: {
                        hit_step = thread->GetFrameCount() == last_step_stack_depth_;
                        break;
                    }
                    default:
                        UNREACHABLE();
                    }
                    if (!hit_step)
                        return;
                    ClearStepping();
                    isolate_->debug()->OnDebugBreak(isolate_->factory()->empty_fixed_array());
                }

                int GetTopPosition(Handle<WasmModuleObject> module_object)
                {
                    DCHECK_EQ(1, interpreter()->GetThreadCount());
                    WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
                    DCHECK_LT(0, thread->GetFrameCount());

                    auto frame = thread->GetFrame(thread->GetFrameCount() - 1);
                    return module_object->GetFunctionOffset(frame->function()->func_index) + frame->pc();
                }

                std::vector<std::pair<uint32_t, int>> GetInterpretedStack(
                    Address frame_pointer)
                {
                    DCHECK_EQ(1, interpreter()->GetThreadCount());
                    WasmInterpreter::Thread* thread = interpreter()->GetThread(0);

                    std::pair<uint32_t, uint32_t> frame_range = GetActivationFrameRange(thread, frame_pointer);

                    std::vector<std::pair<uint32_t, int>> stack;
                    stack.reserve(frame_range.second - frame_range.first);
                    for (uint32_t fp = frame_range.first; fp < frame_range.second; ++fp) {
                        auto frame = thread->GetFrame(fp);
                        stack.emplace_back(frame->function()->func_index, frame->pc());
                    }
                    return stack;
                }

                WasmInterpreter::FramePtr GetInterpretedFrame(Address frame_pointer,
                    int idx)
                {
                    DCHECK_EQ(1, interpreter()->GetThreadCount());
                    WasmInterpreter::Thread* thread = interpreter()->GetThread(0);

                    std::pair<uint32_t, uint32_t> frame_range = GetActivationFrameRange(thread, frame_pointer);
                    DCHECK_LE(0, idx);
                    DCHECK_GT(frame_range.second - frame_range.first, idx);

                    return thread->GetFrame(frame_range.first + idx);
                }

                uint64_t NumInterpretedCalls()
                {
                    DCHECK_EQ(1, interpreter()->GetThreadCount());
                    return interpreter()->GetThread(0)->NumInterpretedCalls();
                }

                Handle<JSObject> GetGlobalScopeObject(InterpretedFrame* frame,
                    Handle<WasmDebugInfo> debug_info)
                {
                    Isolate* isolate = isolate_;
                    Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate);

                    // TODO(clemensh): Add globals to the global scope.
                    Handle<JSObject> global_scope_object = isolate_->factory()->NewJSObjectWithNullProto();
                    if (instance->has_memory_object()) {
                        Handle<String> name = isolate_->factory()->InternalizeOneByteString(
                            StaticCharVector("memory"));
                        Handle<JSArrayBuffer> memory_buffer(
                            instance->memory_object()->array_buffer(), isolate_);
                        Handle<JSTypedArray> uint8_array = isolate_->factory()->NewJSTypedArray(
                            kExternalUint8Array, memory_buffer, 0, memory_buffer->byte_length());
                        JSObject::SetOwnPropertyIgnoreAttributes(global_scope_object, name,
                            uint8_array, NONE)
                            .Assert();
                    }
                    return global_scope_object;
                }

                Handle<JSObject> GetLocalScopeObject(InterpretedFrame* frame,
                    Handle<WasmDebugInfo> debug_info)
                {
                    Isolate* isolate = isolate_;

                    Handle<JSObject> local_scope_object = isolate_->factory()->NewJSObjectWithNullProto();
                    // Fill parameters and locals.
                    int num_params = frame->GetParameterCount();
                    int num_locals = frame->GetLocalCount();
                    DCHECK_LE(num_params, num_locals);
                    if (num_locals > 0) {
                        Handle<JSObject> locals_obj = isolate_->factory()->NewJSObjectWithNullProto();
                        Handle<String> locals_name = isolate_->factory()->InternalizeOneByteString(
                            StaticCharVector("locals"));
                        JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, locals_name,
                            locals_obj, NONE)
                            .Assert();
                        for (int i = 0; i < num_locals; ++i) {
                            MaybeHandle<String> name = GetLocalName(isolate, debug_info, frame->function()->func_index, i);
                            if (name.is_null()) {
                                // Parameters should come before locals in alphabetical ordering, so
                                // we name them "args" here.
                                const char* label = i < num_params ? "arg#%d" : "local#%d";
                                name = PrintFToOneByteString<true>(isolate_, label, i);
                            }
                            WasmValue value = frame->GetLocalValue(i);
                            Handle<Object> value_obj = WasmValueToValueObject(isolate_, value);
                            JSObject::SetOwnPropertyIgnoreAttributes(
                                locals_obj, name.ToHandleChecked(), value_obj, NONE)
                                .Assert();
                        }
                    }

                    // Fill stack values.
                    int stack_count = frame->GetStackHeight();
                    // Use an object without prototype instead of an Array, for nicer displaying
                    // in DevTools. For Arrays, the length field and prototype is displayed,
                    // which does not make too much sense here.
                    Handle<JSObject> stack_obj = isolate_->factory()->NewJSObjectWithNullProto();
                    Handle<String> stack_name = isolate_->factory()->InternalizeOneByteString(
                        StaticCharVector("stack"));
                    JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, stack_name,
                        stack_obj, NONE)
                        .Assert();
                    for (int i = 0; i < stack_count; ++i) {
                        WasmValue value = frame->GetStackValue(i);
                        Handle<Object> value_obj = WasmValueToValueObject(isolate_, value);
                        JSObject::SetOwnElementIgnoreAttributes(
                            stack_obj, static_cast<uint32_t>(i), value_obj, NONE)
                            .Assert();
                    }
                    return local_scope_object;
                }
            };

        } // namespace

    } // namespace wasm

    namespace {

        wasm::InterpreterHandle* GetOrCreateInterpreterHandle(
            Isolate* isolate, Handle<WasmDebugInfo> debug_info)
        {
            Handle<Object> handle(debug_info->interpreter_handle(), isolate);
            if (handle->IsUndefined(isolate)) {
                // Use the maximum stack size to estimate the maximum size of the
                // interpreter. The interpreter keeps its own stack internally, and the size
                // of the stack should dominate the overall size of the interpreter. We
                // multiply by '2' to account for the growing strategy for the backing store
                // of the stack.
                size_t interpreter_size = FLAG_stack_size * KB * 2;
                handle = Managed<wasm::InterpreterHandle>::Allocate(
                    isolate, interpreter_size, isolate, debug_info);
                debug_info->set_interpreter_handle(*handle);
            }

            return Handle<Managed<wasm::InterpreterHandle>>::cast(handle)->raw();
        }

        wasm::InterpreterHandle* GetInterpreterHandle(WasmDebugInfo debug_info)
        {
            Object handle_obj = debug_info->interpreter_handle();
            DCHECK(!handle_obj->IsUndefined());
            return Managed<wasm::InterpreterHandle>::cast(handle_obj)->raw();
        }

        wasm::InterpreterHandle* GetInterpreterHandleOrNull(WasmDebugInfo debug_info)
        {
            Object handle_obj = debug_info->interpreter_handle();
            if (handle_obj->IsUndefined())
                return nullptr;
            return Managed<wasm::InterpreterHandle>::cast(handle_obj)->raw();
        }

        Handle<FixedArray> GetOrCreateInterpretedFunctions(
            Isolate* isolate, Handle<WasmDebugInfo> debug_info)
        {
            Handle<FixedArray> arr(debug_info->interpreted_functions(), isolate);
            int num_functions = debug_info->wasm_instance()
                                    ->module_object()
                                    ->native_module()
                                    ->num_functions();
            if (arr->length() == 0 && num_functions > 0) {
                arr = isolate->factory()->NewFixedArray(num_functions);
                debug_info->set_interpreted_functions(*arr);
            }
            DCHECK_EQ(num_functions, arr->length());
            return arr;
        }

    } // namespace

    Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<WasmInstanceObject> instance)
    {
        DCHECK(!instance->has_debug_info());
        Factory* factory = instance->GetIsolate()->factory();
        Handle<WasmDebugInfo> debug_info = Handle<WasmDebugInfo>::cast(
            factory->NewStruct(WASM_DEBUG_INFO_TYPE, AllocationType::kOld));
        debug_info->set_wasm_instance(*instance);
        debug_info->set_interpreted_functions(*factory->empty_fixed_array());
        instance->set_debug_info(*debug_info);
        return debug_info;
    }

    wasm::WasmInterpreter* WasmDebugInfo::SetupForTesting(
        Handle<WasmInstanceObject> instance_obj)
    {
        Handle<WasmDebugInfo> debug_info = WasmDebugInfo::New(instance_obj);
        Isolate* isolate = instance_obj->GetIsolate();
        // Use the maximum stack size to estimate the maximum size of the interpreter.
        // The interpreter keeps its own stack internally, and the size of the stack
        // should dominate the overall size of the interpreter. We multiply by '2' to
        // account for the growing strategy for the backing store of the stack.
        size_t interpreter_size = FLAG_stack_size * KB * 2;
        auto interp_handle = Managed<wasm::InterpreterHandle>::Allocate(
            isolate, interpreter_size, isolate, debug_info);
        debug_info->set_interpreter_handle(*interp_handle);
        return interp_handle->raw()->interpreter();
    }

    void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info,
        int func_index, int offset)
    {
        Isolate* isolate = debug_info->GetIsolate();
        auto* handle = GetOrCreateInterpreterHandle(isolate, debug_info);
        RedirectToInterpreter(debug_info, Vector<int>(&func_index, 1));
        const wasm::WasmFunction* func = &handle->module()->functions[func_index];
        handle->interpreter()->SetBreakpoint(func, offset, true);
    }

    void WasmDebugInfo::RedirectToInterpreter(Handle<WasmDebugInfo> debug_info,
        Vector<int> func_indexes)
    {
        Isolate* isolate = debug_info->GetIsolate();
        // Ensure that the interpreter is instantiated.
        GetOrCreateInterpreterHandle(isolate, debug_info);
        Handle<FixedArray> interpreted_functions = GetOrCreateInterpretedFunctions(isolate, debug_info);
        Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate);
        wasm::NativeModule* native_module = instance->module_object()->native_module();
        const wasm::WasmModule* module = instance->module();

        // We may modify the wasm jump table.
        wasm::NativeModuleModificationScope native_module_modification_scope(
            native_module);

        for (int func_index : func_indexes) {
            DCHECK_LE(0, func_index);
            DCHECK_GT(module->functions.size(), func_index);
            if (!interpreted_functions->get(func_index)->IsUndefined(isolate))
                continue;

            wasm::WasmCodeRefScope code_ref_scope;
            wasm::WasmCompilationResult result = compiler::CompileWasmInterpreterEntry(
                isolate->wasm_engine(), native_module->enabled_features(), func_index,
                module->functions[func_index].sig);
            std::unique_ptr<wasm::WasmCode> wasm_code = native_module->AddCode(
                func_index, result.code_desc, result.frame_slot_count,
                result.tagged_parameter_slots, std::move(result.protected_instructions),
                std::move(result.source_positions), wasm::WasmCode::kInterpreterEntry,
                wasm::ExecutionTier::kInterpreter);
            Address instruction_start = wasm_code->instruction_start();
            native_module->PublishCode(std::move(wasm_code));

            Handle<Foreign> foreign_holder = isolate->factory()->NewForeign(instruction_start, AllocationType::kOld);
            interpreted_functions->set(func_index, *foreign_holder);
        }
    }

    void WasmDebugInfo::PrepareStep(StepAction step_action)
    {
        GetInterpreterHandle(*this)->PrepareStep(step_action);
    }

    // static
    bool WasmDebugInfo::RunInterpreter(Isolate* isolate,
        Handle<WasmDebugInfo> debug_info,
        Address frame_pointer, int func_index,
        Vector<wasm::WasmValue> argument_values,
        Vector<wasm::WasmValue> return_values)
    {
        DCHECK_LE(0, func_index);
        auto* handle = GetOrCreateInterpreterHandle(isolate, debug_info);
        Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate);
        return handle->Execute(instance, frame_pointer,
            static_cast<uint32_t>(func_index), argument_values,
            return_values);
    }

    std::vector<std::pair<uint32_t, int>> WasmDebugInfo::GetInterpretedStack(
        Address frame_pointer)
    {
        return GetInterpreterHandle(*this)->GetInterpretedStack(frame_pointer);
    }

    wasm::WasmInterpreter::FramePtr WasmDebugInfo::GetInterpretedFrame(
        Address frame_pointer, int idx)
    {
        return GetInterpreterHandle(*this)->GetInterpretedFrame(frame_pointer, idx);
    }

    uint64_t WasmDebugInfo::NumInterpretedCalls()
    {
        auto* handle = GetInterpreterHandleOrNull(*this);
        return handle ? handle->NumInterpretedCalls() : 0;
    }

    // static
    Handle<JSObject> WasmDebugInfo::GetGlobalScopeObject(
        Handle<WasmDebugInfo> debug_info, Address frame_pointer, int frame_index)
    {
        auto* interp_handle = GetInterpreterHandle(*debug_info);
        auto frame = interp_handle->GetInterpretedFrame(frame_pointer, frame_index);
        return interp_handle->GetGlobalScopeObject(frame.get(), debug_info);
    }

    // static
    Handle<JSObject> WasmDebugInfo::GetLocalScopeObject(
        Handle<WasmDebugInfo> debug_info, Address frame_pointer, int frame_index)
    {
        auto* interp_handle = GetInterpreterHandle(*debug_info);
        auto frame = interp_handle->GetInterpretedFrame(frame_pointer, frame_index);
        return interp_handle->GetLocalScopeObject(frame.get(), debug_info);
    }

    // static
    Handle<JSFunction> WasmDebugInfo::GetCWasmEntry(
        Handle<WasmDebugInfo> debug_info, wasm::FunctionSig* sig)
    {
        Isolate* isolate = debug_info->GetIsolate();
        DCHECK_EQ(debug_info->has_c_wasm_entries(),
            debug_info->has_c_wasm_entry_map());
        if (!debug_info->has_c_wasm_entries()) {
            auto entries = isolate->factory()->NewFixedArray(4, AllocationType::kOld);
            debug_info->set_c_wasm_entries(*entries);
            size_t map_size = 0; // size estimate not so important here.
            auto managed_map = Managed<wasm::SignatureMap>::Allocate(isolate, map_size);
            debug_info->set_c_wasm_entry_map(*managed_map);
        }
        Handle<FixedArray> entries(debug_info->c_wasm_entries(), isolate);
        wasm::SignatureMap* map = debug_info->c_wasm_entry_map()->raw();
        int32_t index = map->Find(*sig);
        if (index == -1) {
            index = static_cast<int32_t>(map->FindOrInsert(*sig));
            if (index == entries->length()) {
                entries = isolate->factory()->CopyFixedArrayAndGrow(
                    entries, entries->length(), AllocationType::kOld);
                debug_info->set_c_wasm_entries(*entries);
            }
            DCHECK(entries->get(index)->IsUndefined(isolate));
            Handle<Code> new_entry_code = compiler::CompileCWasmEntry(isolate, sig).ToHandleChecked();
            Handle<WasmExportedFunctionData> function_data = Handle<WasmExportedFunctionData>::cast(isolate->factory()->NewStruct(
                WASM_EXPORTED_FUNCTION_DATA_TYPE, AllocationType::kOld));
            function_data->set_wrapper_code(*new_entry_code);
            function_data->set_instance(debug_info->wasm_instance());
            function_data->set_jump_table_offset(-1);
            function_data->set_function_index(-1);
            Handle<String> name = isolate->factory()->InternalizeOneByteString(
                StaticCharVector("c-wasm-entry"));
            NewFunctionArgs args = NewFunctionArgs::ForWasm(
                name, function_data, isolate->sloppy_function_map());
            Handle<JSFunction> new_entry = isolate->factory()->NewFunction(args);
            new_entry->set_context(debug_info->wasm_instance()->native_context());
            new_entry->shared()->set_internal_formal_parameter_count(
                compiler::CWasmEntryParameters::kNumParameters);
            entries->set(index, *new_entry);
        }
        return handle(JSFunction::cast(entries->get(index)), isolate);
    }

} // namespace internal
} // namespace v8
